/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.rbac.aspect;

import com.alibaba.fastjson2.JSON;
import com.google.common.base.Strings;
import com.je.common.base.context.GlobalExtendedContext;
import com.je.common.base.util.SecurityUserHolder;
import com.je.rbac.annotation.ControllerAuditLog;
import com.je.rbac.service.AuditLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class AuditAspect {

    private static final Logger logger = LoggerFactory.getLogger(AuditAspect.class);

    @Autowired
    private AuditLogService auditLogService;

    @Pointcut("@annotation(com.je.rbac.annotation.ControllerAuditLog)")
    public void pointcut() {

    }

    private String extractRequestIp(Object[] args) {
        for (int i = 0; args != null && i < args.length; i++) {
            if (args[i] instanceof HttpServletRequest) {
                return getIpAddress(((HttpServletRequest) args[i]));
            }
        }
        return null;
    }

    private String extractRequestUrl(Object[] args) {
        for (int i = 0; args != null && i < args.length; i++) {
            if (args[i] instanceof HttpServletRequest) {
                return ((HttpServletRequest) args[i]).getRequestURI();
            }
        }
        return null;
    }

    private String extractResult(String annotationContent, String result) {
        if (result.length() > 3000) {
            result = result.substring(0, 3000);
        }
        if (Strings.isNullOrEmpty(annotationContent)) {
            return "返回结果：" + JSON.toJSONString(result);
        } else {
            return annotationContent + "，返回结果：" + JSON.toJSONString(result);
        }
    }

    private Map<String, Object> extractParams(Object[] args) {
        HttpServletRequest request = null;
        for (int i = 0; args != null && i < args.length; i++) {
            if (args[i] instanceof HttpServletRequest) {
                request = ((HttpServletRequest) args[i]);
                break;
            }
        }
        if (request == null) {
            return null;
        }
        Map<String, Object> params = new HashMap<>();
        for (Map.Entry<String, String[]> eachEntry : request.getParameterMap().entrySet()) {
            params.put(eachEntry.getKey(), eachEntry.getValue() == null ? null : eachEntry.getValue()[0]);
        }
        return params;
    }

    private String substringParams(Map<String, Object> params) {
        String content = JSON.toJSONString(params);
        if (content.length() > 3000) {
            content = content.substring(0, 3000);
        }
        return content;
    }

    private String getIpAddress(HttpServletRequest request) {
        String ip = "";
        try {
            // 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址
            ip = request.getHeader("X-Forwarded-For");
            if (ip != null && ip.length() > 0 && !"unKnown".equalsIgnoreCase(ip)) {
                // 多次反向代理后会有多个ip值，第一个ip才是真实ip
                int index = ip.indexOf(",");
                if (index != -1) {
                    return ip.substring(0, index);
                } else {
                    return ip;
                }
            }
            ip = request.getHeader("X-Real-IP");
            if (ip != null && ip.length() > 0 && !"unKnown".equalsIgnoreCase(ip)) {
                return ip;
            }
            ip = request.getRemoteAddr();
        } catch (Exception e) {

        }
        return ip;
    }

    @AfterReturning(pointcut = "pointcut()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        ControllerAuditLog controllerAuditLogAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ControllerAuditLog.class);
        Map<String, Object> params = extractParams(joinPoint.getArgs());
        String clientIp = extractRequestIp(joinPoint.getArgs());
        String url = extractRequestUrl(joinPoint.getArgs());
        String resultContent = extractResult(controllerAuditLogAnnotation.content(), JSON.toJSONString(result));

        boolean recordFlag = false;
        if ("auditManage".equals(controllerAuditLogAnnotation.logTypeCode())
                && "1".equals(GlobalExtendedContext.getContextKey("auditManagerLogSwitch"))) {
            //如果当前日志是审计管理员操作日志，并启用记录日志
            recordFlag = true;
        }

        if ("securityManage".equals(controllerAuditLogAnnotation.logTypeCode())
                && "1".equals(GlobalExtendedContext.getContextKey("securityManagerLogSwitch"))) {
            //如果当前日志是安全管理员操作日志，并启用记录安全管理日志
            recordFlag = true;
        }

        if ("systemManage".equals(controllerAuditLogAnnotation.logTypeCode())
                && "1".equals(GlobalExtendedContext.getContextKey("systemManagerLogSwitch"))) {
            //如果当前日志是审计管理员操作日志，并启用记录日志
            recordFlag = true;
        }

        if(recordFlag){
            auditLogService.log(clientIp, controllerAuditLogAnnotation.moduleName(), url,
                    controllerAuditLogAnnotation.operateTypeCode(),
                    controllerAuditLogAnnotation.operateTypeName(),
                    controllerAuditLogAnnotation.logTypeCode(),
                    controllerAuditLogAnnotation.logTypeName(),
                    SecurityUserHolder.getCurrentAccountName(),
                    SecurityUserHolder.getCurrentAccountCode(),
                    SecurityUserHolder.getCurrentAccountId(),
                    "调用成功", substringParams(params), resultContent);
        }
        logger.info("client ip {} operate module {} success, record {},operaterType {},logType {},result {}", clientIp,
                controllerAuditLogAnnotation.moduleName(),recordFlag, controllerAuditLogAnnotation.operateTypeCode(),
                controllerAuditLogAnnotation.logTypeCode(), resultContent);
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "ex")
    public void afterThrow(JoinPoint joinPoint, Throwable ex) {
        ControllerAuditLog controllerAuditLogAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ControllerAuditLog.class);
        Map<String, Object> params = extractParams(joinPoint.getArgs());
        String clientIp = extractRequestIp(joinPoint.getArgs());
        String url = extractRequestUrl(joinPoint.getArgs());
        String resultContent = extractResult(controllerAuditLogAnnotation.content(), ex.toString());
        auditLogService.log(clientIp, controllerAuditLogAnnotation.moduleName(), url,
                controllerAuditLogAnnotation.operateTypeCode(),
                controllerAuditLogAnnotation.operateTypeName(),
                controllerAuditLogAnnotation.logTypeCode(),
                controllerAuditLogAnnotation.logTypeName(),
                SecurityUserHolder.getCurrentAccountName(),
                SecurityUserHolder.getCurrentAccountCode(),
                SecurityUserHolder.getCurrentAccountId(),
                "调用异常", substringParams(params), resultContent);
        logger.info("client ip {} operate module {} exception,operaterType {},logType {},result {}", clientIp,
                controllerAuditLogAnnotation.moduleName(), controllerAuditLogAnnotation.operateTypeCode(), controllerAuditLogAnnotation.logTypeCode(), resultContent);
    }

}
